#include <helib/helib.h>

int main(int argc, char *argv[]) {
  helib::Context context = helib::ContextBuilder<helib::CKKS>()
                               .m(32 * 1024)
                               .bits(358)
                               .precision(43)
                               .c(6)
                               .build();

  std::cout << "* N=" << context.getPhiM() << "\n";
  std::cout << "* bitSizeOfQ=" << context.bitSizeOfQ() << "\n";
  std::cout << "* # of KS-Digits=" << context.getDigits().size() << "\n";
  std::cout << "* securityLevel=" << context.securityLevel() << "\n\n";

  long n = context.getNSlots();

  helib::SecKey secretKey(context);
  secretKey.GenSecKey();
  const helib::PubKey &publicKey = secretKey;

  //===========================================================================

  // Let's encrypt something!
  std::vector<double> v(n);
  std::vector<double> vv(n);
  for (long i = 0; i < n; i++) {
    v[i] = 1.0 + static_cast<double>(i % 6);
  }
  std::cout.precision(16);
  for (auto i = 0; i < 6l; i++) {
    std::cout << "p[" << i << "]=" << v[i] << std::endl;
  }
  std::cout << "..." << std::endl;
  helib::PtxtArray p(context, v);
  helib::Ctxt c(publicKey);
  p.encrypt(c);

  std::cout << "c.capacity=" << c.capacity() << "\t";
  std::cout << "c.errorBound=" << c.errorBound() << "\n";

  //===========================================================================

  // Let's square c a few times and see what happens

  std::cout << "\nHMUL 1: c *= c" << std::endl;
  c *= c;
  std::cout << "c.capacity=" << c.capacity() << "\t";
  std::cout << "c.errorBound=" << c.errorBound() << "\t";
  p *= p;
  helib::PtxtArray pp(context);
  pp.decrypt(c, secretKey);
  std::cout << "distance(p,Dec(c))=" << Distance(p, pp) << std::endl;
  p.store(v);
  pp.store(vv);
  for (auto i = 0; i < 6l; i++) {
    std::cout << "p[" << i << "]=" << v[i] << "\t"
              << "Dec(c)[" << i << "]=" << vv[i] << std::endl;
    ;
  }
  std::cout << "..." << std::endl;

  std::cout << "\nHMUL 2: c *= c" << std::endl;
  c *= c;
  std::cout << "c.capacity=" << c.capacity() << "\t";
  std::cout << "c.errorBound=" << c.errorBound() << "\t";
  p *= p;
  pp.decrypt(c, secretKey);
  std::cout << "distance(p,Dec(c))=" << Distance(p, pp) << std::endl;
  p.store(v);
  pp.store(vv);
  for (auto i = 0; i < 6l; i++) {
    std::cout << "p[" << i << "]=" << v[i] << "\t"
              << "Dec(c)[" << i << "]=" << vv[i] << std::endl;
    ;
  }
  std::cout << "..." << std::endl;

  std::cout << "\nHMUL 3: c *= c" << std::endl;
  c *= c;
  std::cout << "c.capacity=" << c.capacity() << "\t";
  std::cout << "c.errorBound=" << c.errorBound() << "\t";
  p *= p;
  pp.decrypt(c, secretKey);
  std::cout << "distance(p,Dec(c))=" << Distance(p, pp) << std::endl;
  p.store(v);
  pp.store(vv);
  for (auto i = 0; i < 6l; i++) {
    std::cout << "p[" << i << "]=" << v[i] << "\t"
              << "Dec(c)[" << i << "]=" << vv[i] << std::endl;
    ;
  }
  std::cout << "..." << std::endl;

  std::cout << "\nHMUL 4: c *= c" << std::endl;
  c *= c;
  std::cout << "c.capacity=" << c.capacity() << "\t";
  std::cout << "c.errorBound=" << c.errorBound() << "\t";
  p *= p;
  pp.decrypt(c, secretKey);
  std::cout << "distance(p,Dec(c))=" << Distance(p, pp) << std::endl;
  p.store(v);
  pp.store(vv);
  for (auto i = 0; i < 6l; i++) {
    std::cout << "p[" << i << "]=" << v[i] << "\t"
              << "Dec(c)[" << i << "]=" << vv[i] << std::endl;
    ;
  }
  std::cout << "..." << std::endl;

  std::cout << "\nHMUL 5: c *= c" << std::endl;
  c *= c;
  std::cout << "c.capacity=" << c.capacity() << "\t";
  std::cout << "c.errorBound=" << c.errorBound() << "\t";
  p *= p;
  pp.decrypt(c, secretKey);
  std::cout << "distance(p,Dec(c))=" << Distance(p, pp) << std::endl;
  p.store(v);
  pp.store(vv);
  for (auto i = 0; i < 6l; i++) {
    std::cout << "p[" << i << "]=" << v[i] << "\t"
              << "Dec(c)[" << i << "]=" << vv[i] << std::endl;
    ;
  }
  std::cout << "..." << std::endl;

  //===========================================================================

  // Let's decrypt and compare:

  //===========================================================================

  // On my machine, I get the following output:
  //
  // c.capacity=328.497 c.errorBound=1.28242e-06
  // c.capacity=289.748 c.errorBound=2.69423e-06
  // c.capacity=252.063 c.errorBound=5.71405e-06
  // c.capacity=213.502 c.errorBound=1.1591e-05
  // c.capacity=176.579 c.errorBound=2.37053e-05
  // c.capacity=139.634 c.errorBound=4.79147e-05
  // distance=1.84256e-05
  //
  // So we see that we start out with capacity about 328 (which is somewhat
  // less than the value 358 of the bits parameter), and an errorBound of
  // 1.28242e-06, which is slightly larger than 2^{-20} = 2^{-precision}.
  // After each squaring, capacity decreases by 37-39, while errorBound
  // increases by about a factor of 2 (i.e., we lose one bit of precision).
  // Finally, when we decrypt, we see the actual error (1.84256e-05) is
  // somewhat smaller than errorBound (4.79147e-05).
  //
  // Note that the values returned by capacity() and errorBound() may vary
  // from one run of the program to another, even if all the parameters and
  // plaintext data are the same.  However, they should not change by much
  // from one run to another.

  return 0;
}

// int main(int argc, char *argv[]) {
//   helib::Context context = helib::ContextBuilder<helib::CKKS>()
//                                .m(16 * 1024)
//                                .bits(407)
//                                .precision(24)
//                                .c(8)
//                                .build();
//   context.printout();

//   long n = context.getNSlots();

//   helib::SecKey secretKey(context);
//   secretKey.GenSecKey();
//   const helib::PubKey &publicKey = secretKey;

//   //===========================================================================

//   // Let's encrypt something!
//   std::vector<double> v(n);
//   for (long i = 0; i < n; i++)
//     v[i] = sin(2.0 * helib::PI * i / n);
//   helib::PtxtArray p(context, v);
//   helib::Ctxt c(publicKey);
//   p.encrypt(c);

//   std::vector<double> vv(n);
//   for (long i = 0; i < n; i++)
//     vv[i] = cos(2.0 * helib::PI * i / n);
//   helib::PtxtArray pp(context, v);
//   helib::Ctxt cc(publicKey);
//   pp.encrypt(cc);

//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << c.errorBound() << "\n";
//   auto pre = c.errorBound();

//   std::cout << "cc.capacity=" << cc.capacity() << " ";
//   std::cout << "cc.errorBound=" << cc.errorBound() << "\n";

//   //===========================================================================

//   // Let's square c a few times and see what happens

//   // 1 HMULT
//   c *= cc;
//   auto cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 49 HADD
//   auto c_copy = c;
//   for (int i = 1; i < 49; i++) {
//     c += c_copy;
//   }
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 1 HMULT
//   c.square();
//   // c *= c;
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 1 HMULT
//   c.square();
//   // c *= c;
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 64 HADD
//   c_copy = c;
//   for (int i = 1; i < 64; i++) {
//     c += c_copy;
//   }
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 4 HADD
//   c_copy = c;
//   for (int i = 1; i < 4; i++) {
//     c += c_copy;
//   }
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 1 HMULT
//   c.square();
//   // c *= c;
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 1 HMULT
//   c.square();
//   // c *= c;
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";
//   pre = cur;

//   // 64 HADD
//   c_copy = c;
//   for (int i = 1; i < 64; i++) {
//     c += c_copy;
//   }
//   cur = c.errorBound();
//   std::cout << "c.capacity=" << c.capacity() << " ";
//   std::cout << "c.errorBound=" << cur << "(x" << cur / pre << ")\n";

//   //===========================================================================

//   // Let's perform the same computation on the plaintext:
//   // 1 HMULT
//   p *= pp;
//   // 49 HADD
//   auto p_copy = p;
//   for (int i = 1; i < 49; i++) {
//     p += p_copy;
//   }
//   // 1 HMULT
//   p *= p;
//   // 1 HMULT
//   p *= p;
//   // 64 HADD
//   p_copy = p;
//   for (int i = 1; i < 64; i++) {
//     p += p_copy;
//   }
//   // 4 HADD
//   p_copy = p;
//   for (int i = 1; i < 4; i++) {
//     p += p_copy;
//   }
//   // 1 HMULT
//   p *= p;
//   // 1 HMULT
//   p *= p;
//   // 64 HADD
//   p_copy = p;
//   for (int i = 1; i < 64; i++) {
//     p += p_copy;
//   }

//   //===========================================================================

//   // Let's decrypt and compare:
//   helib::PtxtArray p_dec(context);
//   p_dec.decrypt(c, secretKey);

//   double distance = Distance(p, p_dec);
//   std::cout << "distance=" << distance << "\n";

//   //===========================================================================

//   return 0;
// }